package uk.ac.aber.Blockmation;

import uk.ac.aber.Blockmation.ColorsTable.ColorsTable;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;

import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;

/**
 * The heart of GIFted Blockmation application.
 * All GUI classes are created in the constructor of this class, the whole
 * layout is designed here as well.
 * Look up the documentation to get more information about this class.  
 * @author GIFted Group
 *
 */
public class MainFrame extends JFrame implements AdjustmentListener,
    WindowListener, ComponentListener
{
    private static final Dimension APP_DIMENSION = new Dimension(600, 600);
    private static final String APP_NAME = "GIFted Blockmation by TAZA";
    private DrawingPad pad;
    private MovieModel movieModel;
    private int currentFrameIndex;
    private File projectFile;
    private OurFileChooser ourFileChooser;
    //private FileManager fileManager;
    private OurMenu ourMenu;
    private OurToolBar ourToolBar;
    private Viewer viewer;
    private ColorsTable colorsTable;

    /*
     * for keeping track of changes, so app won't let you close without
     * asking for saving first
     */
    private boolean changesSavedToFile = true;
    private BufferedImage copiedFrame;

    // Swing GUI
    JScrollPane drawingSpace;
    JScrollBar navigationBar;
    private JLabel statusMsg;
    private JPanel statusPanel;

    // user-end GUI components
    private RightPanel rightPanel;
    private CenterBottomPanel centerBottomPanel;
    private LeftPanel leftPanel;
    
    // New Project Dialog
    private JDialog newProjectDialog;
    

    /**
     * Creates the whole MainFrame object as the centre of application.
     * Sets every Swing component ready and lays them out in GUI.
     * Initialises variables/references used throughout application. 
     */
    public MainFrame()
    {
        super(APP_NAME);

        // start off from scratch
        ourMenu = new OurMenu(this);
        ourToolBar = new OurToolBar(this);
        ourFileChooser = new OurFileChooser(this);
        //fileManager = new FileManager();
        projectFile = null;
        movieModel = new MovieModel();
        pad = new DrawingPad(this);
        movieModel.appendFrame(pad.getImage());
        currentFrameIndex = 0; // default index
        viewer = new Viewer(movieModel);
        viewer.createGUI();
        colorsTable = new ColorsTable(this);

        // asks user to save project first, cannot exit without prompt!  
        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        addWindowListener(this);
        addComponentListener(this);

        drawingSpace = new JScrollPane(pad);

        navigationBar = new JScrollBar(JScrollBar.HORIZONTAL, 0, 0, 0, 0);
        navigationBar.addAdjustmentListener(this);
        navigationBar.setBlockIncrement(1);

        statusMsg = new JLabel(" 1 / 1");
        statusPanel = new JPanel();
        statusPanel.setLayout(new FlowLayout());
        statusPanel.add(statusMsg);
        statusPanel.setBorder(BorderFactory.createEtchedBorder());
        
        
    	rightPanel = new RightPanel(this);
    	centerBottomPanel = new CenterBottomPanel(this);
    	leftPanel = new LeftPanel(this);

        ////////////////////////
        ////CREATING A PANEL FOR ALL THE CENTER OBJECTS TOP TO BOTTOM
        JPanel topPane = new JPanel();
        topPane.setLayout(new BoxLayout(topPane, BoxLayout.PAGE_AXIS));

        topPane.add(drawingSpace);
        drawingSpace.setAlignmentX(drawingSpace.CENTER_ALIGNMENT);
        drawingSpace.setAlignmentY(drawingSpace.CENTER_ALIGNMENT);
        topPane.add(Box.createRigidArea(new Dimension(10,10)));

        topPane.add(navigationBar);
        navigationBar.setAlignmentX(navigationBar.CENTER_ALIGNMENT);
        navigationBar.setAlignmentY(navigationBar.CENTER_ALIGNMENT);
        topPane.add(Box.createRigidArea(new Dimension(10,10)));

        topPane.add(centerBottomPanel);
        centerBottomPanel.setAlignmentX(centerBottomPanel.CENTER_ALIGNMENT);
        centerBottomPanel.setAlignmentY(centerBottomPanel.CENTER_ALIGNMENT);
        centerBottomPanel.setPreferredSize(new Dimension(100, 100));///w,h
        topPane.add(Box.createRigidArea(new Dimension(10,10)));
        
        statusPanel.setMaximumSize(new Dimension(100, 20));
        topPane.add(statusPanel);
        statusPanel.setAlignmentX(statusPanel.CENTER_ALIGNMENT);
        statusPanel.setAlignmentY(statusPanel.CENTER_ALIGNMENT);
        topPane.add(Box.createRigidArea(new Dimension(10,10)));
        
        
        ////Panel holding all LEFT SIDE components   
        JPanel leftToRightpanel = new JPanel();
        
        //leftToRightpanel.setLayout(new BoxLayout(leftToRightpanel, BoxLayout.X_AXIS));

        leftToRightpanel.add(Box.createRigidArea(new Dimension(3,10)));
        
        leftToRightpanel.add(leftPanel);
        leftPanel.setAlignmentX(leftPanel.CENTER_ALIGNMENT);
        leftPanel.setAlignmentY(leftPanel.CENTER_ALIGNMENT);
        leftPanel.setPreferredSize(new Dimension(100, 350)); ///w,l
        leftToRightpanel.add(Box.createRigidArea(new Dimension(20, 10)));


        leftToRightpanel.setLayout(new BoxLayout(leftToRightpanel, BoxLayout.X_AXIS));

        
        ///////////
        //////////////////Panel Holding Right Side Components
    JPanel rightToLeftpanel = new JPanel();

    rightToLeftpanel.setLayout(new BoxLayout(rightToLeftpanel, BoxLayout.X_AXIS));

	rightToLeftpanel.add(Box.createRigidArea(new Dimension(20,10)));

    rightToLeftpanel.add(rightPanel);

 			rightPanel.setAlignmentX(leftPanel.CENTER_ALIGNMENT);
 			rightPanel.setAlignmentY(leftPanel.CENTER_ALIGNMENT);
 			rightPanel.setPreferredSize(new Dimension(100, 350)); ///w,l
     
 			rightToLeftpanel.add(Box.createRigidArea(new Dimension(3,10)));

 			rightToLeftpanel.setLayout(new BoxLayout(rightToLeftpanel, BoxLayout.X_AXIS));

/////////////
//////////////////Panel Holding TOP Components


        JPanel topOfPage = new JPanel();
        topOfPage.setLayout(new BoxLayout(topOfPage, BoxLayout.PAGE_AXIS));
        
        this.setJMenuBar( ourMenu.getOurMenu() );
        
        topOfPage.add(ourToolBar);
  
        ourToolBar.setPreferredSize(new Dimension(2, 30));
        topOfPage.add(Box.createRigidArea(new Dimension(0,10)));
        
        ///////////////////
        ////////ADDING ALL COMPONENTS TO CONTAINER

        Container contentPane = getContentPane();
        contentPane.add(topPane, BorderLayout.CENTER);
        contentPane.add(leftToRightpanel, BorderLayout.WEST);
        contentPane.add(rightToLeftpanel, BorderLayout.EAST);
        contentPane.add(topOfPage, BorderLayout.NORTH);

        
        //remember to save current frame to model as some point
        //and 
        //best when user leaves drawingSpace I think, least invassive.
        
        pack();
        //setSize(APP_DIMENSION);
        setLocationRelativeTo(null);

    }

    /**
     * Loads new animation project. Opens up dialog to choose file to
     * load. File types are specified in OurFileChooser class. \n If necessary
     * prompts to save changes to existing project first.
     */
    public void loadNewProject()
    {
        // if existing project has not been saved...
    	if (!changesSavedToFile)
        {
        	askToSave();
        }
    	
    	// and only then load new project
    	projectFile = ourFileChooser.loadProject();
    	MovieModel loaded = new MovieModel();
        loaded = FileManager.loadMovie(projectFile);
        
        if (loaded == null)
        {
        	System.err.println("File coudn't be loaded.");
        	// inform the user 
        } else
        {
        	movieModel = loaded;
        	updateAppWithNewModel();
        }
    }

    /**
     * Whenever model changes completely call this method to ensure
     * relevant changes in whole application.
     */
    public void updateAppWithNewModel()
    {
        if (movieModel != null)
        {
            BufferedImage image = movieModel.get(0);

            if (projectFile == null)
            {
            	setTitle(APP_NAME + " [" + "Unsaved Project" + "]");
            } else 
            {
            	setTitle(APP_NAME + " [" + projectFile.getAbsolutePath() + "]");
            }
            
/*            int spaceWidth = drawingSpace.getWidth();
            int spaceHeight = drawingSpace.getHeight();
            int blockWidth = spaceWidth / image.getWidth();
            int blockHeight = spaceHeight / image.getHeight();
            
            Dimension blockDimension = new Dimension(blockWidth, blockHeight);
            pad.setBlockSize(blockDimension);*/
            
            pad.setImage(image); 
            updateStatus();
            viewer.updateNewModel(movieModel);
        }
        else
        {
            System.err.println("No model available. Load or create one.");
        }
    }

    /**
     * Saves existing project into opened file or asks to select  a
     * file and saves.
     */
    public void saveProject()
    {
        if ((projectFile != null) && projectFile.exists())
        {
 
            if ( FileManager.save(movieModel, projectFile) == null)
            {
            	// inform the user
            	System.err.println("Error: File hasn't been saved.");
            	return;
            } else
            {
            	System.out.println("Current project saved to file.");
            }
        }
        else
        {
            saveProjectAs();
        }
        changesSavedToFile = true;
    }

    /**
     * Saves project as one of available file formats.
     */
    public void saveProjectAs()
    {
		// get it from file chooser!
    	File file = ourFileChooser.saveProjectAs();
        
        if ( FileManager.save(movieModel, file) == null)
        {
            System.err.println("Error. File hasn't been saved.");
            return;
            // inform the user
        } else
        {
        	System.out.println("Project saved as..." + file);
        }
	}


    /**
     * Exports current frame as an image to one of the available in OS
     * image format files provided by ImageIO.write(...) method.
     * Saves the drawingPad image rather than the one stored in movieModel!
     */
    public void exportPadImage()
    {
		// get it from file chooser!
    	File file = ourFileChooser.exportFrameAsImage();
    	
    	if (file == null)
    	{
    		System.err.println("Canceled or I/O error. Image hasn't bee exported.");
    		return;
    	}
    	
        String ext = FileManager.getExtFromFile(file);

        try
        {
            ImageIO.write(pad.getImage(), ext, file.getAbsoluteFile());
        }
        catch (IOException e)
        {
            System.err.println(e.getMessage());

            String msg = "Frame could not be exported. \n" + e.getMessage();
            JOptionPane.showMessageDialog(this, msg, "Error",
                JOptionPane.ERROR_MESSAGE);
        }

        System.out.println("Image exported.");
    }

    /**
     * Imports an existing image into the current frame. Overrides
     * existing frame.
     */
    public void importImageIntoPad()
    {
		// get it from file chooser!
    	File file = ourFileChooser.importImageIntoFrame();
        try
        {
            pad.setImage(ImageIO.read(file));
    		movieModel.setFrame(currentFrameIndex, 
    				pad.getImage());
        }
        catch (IOException e)
        {
            System.err.println(e.getMessage());

            String msg = "Image could not be imported. \n" + e.getMessage();
            JOptionPane.showMessageDialog(this, msg, "Error",
                JOptionPane.ERROR_MESSAGE);
        }
        changesSavedToFile = false;
        System.out.println("Image imported.");
        updateStatus();
    }

    /**
     * Resets whole application to work on a brand new movie. Checks
     * whether it's necessary to save changes in existing project first.
     */
    public void startNewProject()
    {
        // set the dimension of frames, colors, etc...
        // set up the model
        if (!changesSavedToFile)
        {
        	askToSave();
        }
           
        	// open dialog window and let the user define the values below
        	// e.i. width, height and background color of new Animation
        
        // document-modal dialog box //
        newProjectDialog = new NewProjectDialog(this);

    }

    public void adjustmentValueChanged(AdjustmentEvent e)
    {
        System.out.println("Adjusted: " + e.getValue());
        
        this.setCurrentFrameIndex(e.getValue());
        updateStatus();
    }

    public MovieModel getMovieModel()
    {
        return movieModel;
    }

    public DrawingPad getPad()
    {
        return pad;
    }
    
    public CenterBottomPanel getCenterBottomPanel() {
        return centerBottomPanel;
    }
    
    public Viewer getViewer() {
        return viewer;
    }

    public int getCurrentFrameIndex()
    {
        return currentFrameIndex;
    }

    /**
     * Sets the currently seen frame to the one at newFrameIndex and
     * loads it onto the drawingPad from model. Before it does, saves changes
     * to the current frame into model.
     *
     * @param newFrameIndex - the index of a frame from movieModel that  you
     *        want the drawingPad to display.
     */
    public void setCurrentFrameIndex(int newFrameIndex)
    {
        if (withinModel(newFrameIndex))
        {
            // saving current frame in model 
            //movieModel.setFrame(this.currentFrameIndex, pad.getImage());
            // setting index frame at newFrameIndex
            this.currentFrameIndex = newFrameIndex;
            // loading image at the new index from model
            this.pad.setImage(movieModel.get(newFrameIndex));
            changesSavedToFile = false;
            navigationBar.setValue(currentFrameIndex);
            updateStatus();
            centerBottomPanel.updateImages();
            centerBottomPanel.updateCurrentThumb();
        }
        else
        {
            System.out.format("Index %d beyond model index 0 and %d",
                newFrameIndex, movieModel.size() - 1);
        }
    }
    
    /**
     * Checks whether index lies within bounds object of MovieModel index.
     * @param index - to be checked
     * @return true if it does, false otherwise
     */
    public boolean withinModel(int index)
    {
        return ((index >= 0) && (index < movieModel.size())) ? true : false;
    }
    
    /**
     * Shows up dialog with prompt to save current project.
     * If YES has been clicked, project will save, otherwise won't.
     * Then method returns with JOptionPane constant.
     * @return result from JOptionPane as its constant
     */
    private int askToSave()
    {
        int n = JOptionPane.showConfirmDialog(this,
                "Do you want to save the existing project?", "Unsaved project!",
                JOptionPane.YES_NO_CANCEL_OPTION);

        if (n == JOptionPane.YES_OPTION)
        {
            saveProject();
        }
        
        return n;
    }
    
    /**
     * Catching window closing event and prompting to save project if 
     * it's not been saved yet.
     */
    public void windowClosing(WindowEvent e)
    {
        if (!changesSavedToFile)
        {
            // if user's clicked Cancel, do nothing & come back to application
        	if ( askToSave() == JOptionPane.CANCEL_OPTION )
            	return;
        }

        System.out.println("Application closed. Bye!");
        /*
         * changes have been saved. Disposes this MainFrame window 
         * and terminates application.
         * Read the NOTE in dispose() method here:
         * http://java.sun.com/j2se/1.5.0/docs/api/java/awt/Window.html#dispose()
         */ 
        dispose();
        System.exit(0);
    }

    public void windowActivated(WindowEvent e)
    {
    }

    public void windowClosed(WindowEvent e)
    {
    }

    public void windowDeactivated(WindowEvent e)
    {
    }

    public void windowDeiconified(WindowEvent e)
    {
    }

    public void windowIconified(WindowEvent e)
    {
    }

    public void windowOpened(WindowEvent e)
    {
    }

    public BufferedImage getCopiedFrame() {
            return copiedFrame;
    }

    public void setCopiedFrame(BufferedImage copiedFrame) {
            this.copiedFrame = copiedFrame;
    }

    /**
     * Updates component to reflect changes in MovieModel object.
     */
    public void updateStatus() {
        navigationBar.setMaximum(movieModel.size() - 1);
        this.statusMsg.setText((currentFrameIndex+1) + " / " + movieModel.size());
        navigationBar.setValue(currentFrameIndex);
    }

    public JScrollBar getNavigationBar() {
            return navigationBar;
    }

    public JScrollPane getDrawingSpace() {
            return drawingSpace;
    }
    
    
    public Color getPaletteColor()
    {
    	return colorsTable.getSelectedColor();
    }

    public ColorsTable getMyTable() {

            return colorsTable;
    }
        
    public void newMovieModel() {
        this.movieModel = new MovieModel();
    }
    
    public void setChangesSavedToFile(boolean changesSavedToFile) {
        this.changesSavedToFile = changesSavedToFile;
    }

    public JDialog getCreateNewDialog() {
            return newProjectDialog;
    }

    /**
     * Resizes the displayed frame to best fit the drawingSpace.
     */
    public void setFrameBestFit() {

            int availWidth = drawingSpace.getWidth();
            int availHeight = drawingSpace.getHeight();

            int frameWidth = pad.getImage().getWidth();
            int frameHeight = pad.getImage().getHeight();

            int newSize;

            newSize = (int)Math.min((availWidth/frameWidth), (availHeight / frameHeight));

            pad.setBlockSize(new Dimension(newSize, newSize));
            leftPanel.getBlockSizeSpinner().getModel().setValue( (int)pad.getBlockSize().getWidth());

    }

    public void componentHidden(ComponentEvent arg0) {
            // TODO Auto-generated method stub

    }

    public void componentMoved(ComponentEvent arg0) {
            // TODO Auto-generated method stub

    }

    /**
     * When application window resized, resizes the 
     * displayed frame to best fit the drawingSpace.
     */
    public void componentResized(ComponentEvent arg0) {

            setFrameBestFit();		
    }

    public void componentShown(ComponentEvent arg0) {
            // TODO Auto-generated method stub

    }

}
